{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Nonlinear Transformations"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The real world is filled with non-linearities and so dealing with it often requires nonlinear computation. This model shows how to compute nonlinear functions using Nengo 2.0. The two nonlinear functions demonstrated in this model are 'multiplication' and 'squaring'."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "# Setup the environment\n",
    "import matplotlib.pyplot as plt\n",
    "%matplotlib inline\n",
    "\n",
    "import nengo\n",
    "from nengo.dists import Choice\n",
    "from nengo.utils.functions import piecewise"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Create the Model"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The parameters of the model are as described in the book. The model has five ensembles: two input ensembles (`X` and `Y`), a 2D combined ensemble (`vector2D`), and the output ensembles `result_square` and `result_product` which store the square and product of the inputs respectively. \n",
    "\n",
    "Two varying scalar values are used for the two input signals that drive activity in ensembles A and B. For multiplication, you will project both inputs independently into a 2D space, and then decode a nonlinear transformation of that space (i.e., the product) into an ensemble (`result_product`). The model also squares the value of the first input (`inputX`) encoded in an ensemble (X), in the output of another ensemble (`result_square`). \n",
    "\n",
    "The two functions `product(x)` and `square(x)` are defined to serve the same purpose as entering the expressions in the \"Expression\" field in the \"User-defined Function\" dialog box in Nengo 1.4 as described in the book.\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "model = nengo.Network(label='Nonlinear Function')\n",
    "with model:\n",
    "    # Input - Piecewise step functions\n",
    "    inputX = nengo.Node(piecewise({0: -0.75, 1.25: 0.5, 2.5: -0.75, 3.75: 0}))\n",
    "    inputY= nengo.Node(piecewise({0: 1, 1.25: 0.25, 2.5: -0.25, 3.75: 0.75}))\n",
    "    \n",
    "    # Five ensembles containing LIF neurons\n",
    "    X = nengo.Ensemble(100, dimensions=1, radius=1)               # Represents inputX\n",
    "    Y = nengo.Ensemble(100, dimensions=1, radius=1)               # Represents inputY\n",
    "    vector2D = nengo.Ensemble(224, dimensions=2, radius=2)        # 2D combined ensemble\n",
    "    result_square = nengo.Ensemble(100, dimensions=1, radius=1)   # Represents the square of X\n",
    "    result_product = nengo.Ensemble(100, dimensions=1, radius=1)  # Represents the product of X and Y\n",
    "    \n",
    "    # Connecting the input nodes to the appropriate ensembles\n",
    "    nengo.Connection(inputX, X)\n",
    "    nengo.Connection(inputY, Y)\n",
    "    \n",
    "    # Connecting input ensembles A and B to the 2D combined ensemble\n",
    "    nengo.Connection(X, vector2D[0])\n",
    "    nengo.Connection(Y, vector2D[1])\n",
    "    \n",
    "    # Defining a function that computes the product of two inputs\n",
    "    def product(x):\n",
    "        return x[0] * x[1]\n",
    "    \n",
    "    # Defining the squaring function\n",
    "    def square(x):\n",
    "        return x[0] * x[0]\n",
    "    \n",
    "    # Connecting the 2D combined ensemble to the result ensemble \n",
    "    nengo.Connection(vector2D, result_product, function=product)\n",
    "    \n",
    "    # Connecting ensemble A to the result ensemble\n",
    "    nengo.Connection(X, result_square, function=square)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Step 5: Run the Model"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "# Import the nengo_gui visualizer to run and visualize the model.\n",
    "from nengo_gui.ipython import IPythonViz\n",
    "IPythonViz(model, \"ch3-nonlinear.py.cfg\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Press the play button in the visualizer to run the simulation. You should see the graphs as shown in the figure below.\n",
    "\n",
    "The input signals chosen clearly show that the model works well. The result_product graph shows the product of the `inputX` & `inputY`, and the `result_square` graph shows the squre of `inputX`. You can see in the graphs that when inputX is zero, both the product and the square are also zero. You can use the sliders to change the input values provided by the `inputX` and `inputY` nodes to test the model."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "from IPython.display import Image\n",
    "Image(filename='ch3-nonlinear.png')"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 2",
   "language": "python",
   "name": "python2"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 2
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython2",
   "version": "2.7.10"
  },
  "widgets": {
   "state": {},
   "version": "1.1.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 0
}
